Skip to content

feat: Add Config.with_wrapper_information - #501

Merged
kinyoklion merged 2 commits into
mainfrom
devin/config-with-wrapper-information
Aug 20, 2026
Merged

feat: Add Config.with_wrapper_information#501
kinyoklion merged 2 commits into
mainfrom
devin/config-with-wrapper-information

Conversation

@kinyoklion

@kinyoklion kinyoklion commented Aug 19, 2026

Copy link
Copy Markdown
Member

Adds Config.with_wrapper_information, so wrapper libraries can derive a config that identifies themselves without reaching into Config internals.

  • New public method returning a copy of the config with wrapper_name/wrapper_version replaced
  • The original config is left unmodified
  • Mirrors Configuration.Builder(config).WrapperInfo(...) in .NET and LDConfig.Builder.fromConfig(config).wrapper(...) in Java

Requirements

  • I have added test coverage for new or changed functionality
  • I have followed the repository's pull request submission guidelines
  • I have validated my changes against all supported platform versions
Implementation details

Related issues

Needed by openfeature-python-server#51, where the OpenFeature provider must report itself as the wrapper. Wrapper information can only be supplied to the Config constructor today, so a provider that accepts an application-supplied Config has no supported way to add it. The .NET and Java providers do this through their SDK's builder-from-config API; Python has no equivalent.

copy_with_new_sdk_key is the closest existing method, but it is deprecated and rebuilds the config by hand, so it silently drops anything added since it was written (application, hooks, plugins, datasystem_config, and others).

Describe the solution you've provided

def with_wrapper_information(self, wrapper_name, wrapper_version=None) -> 'Config':
    updated = copy.copy(self)
    updated.__wrapper_name = wrapper_name
    updated.__wrapper_version = wrapper_version

    return updated

Shallow-copying avoids the maintenance hazard of re-listing every constructor parameter: fields added later are carried over automatically. Component references (feature store, hooks, plugins) are shared with the original config, which matches how a config is used — the derived config is what gets handed to LDClient, and the original is not used to build a second client.

Describe alternatives you've considered

  • A general copy_with(**kwargs): broader surface area than needed, and re-exposes the constructor's parameter list to the same drift problem.
  • A Config.Builder-style API matching .NET/Java: a much larger change to a long-standing public API, and not required to unblock the providers.
  • Letting wrappers keep assigning the name-mangled private fields: works, but depends on implementation details of the SDK.

Testing

test_with_wrapper_information covers replacing both fields while leaving unrelated settings and the original config intact; test_with_wrapper_information_defaults_the_version covers omitting the version.

Link to Devin session: https://app.devin.ai/sessions/0c452d209ec54b068ba120b4c92b8f6c
Requested by: @kinyoklion


Note

Overview
Adds Config.with_wrapper_information so wrapper libraries (e.g. OpenFeature providers) can stamp wrapper_name / wrapper_version onto an application-supplied config without reconstructing it or touching private fields.

The method shallow-copies the existing Config, replaces those two fields, and leaves the original instance unchanged. Shared objects such as the feature store and HTTP config are not cloned. Tests cover replacement of both fields and omitting the version.

Reviewed by Cursor Bugbot for commit 6db02bf. Bugbot is set up for automated code reviews on this repo. Configure here.

Co-Authored-By: rlamb@launchdarkly.com <4955475+kinyoklion@users.noreply.github.com>
@kinyoklion kinyoklion self-assigned this Aug 19, 2026
@devin-ai-integration

Copy link
Copy Markdown
Contributor

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

@devin-ai-integration

Copy link
Copy Markdown
Contributor

@cursor review

Co-Authored-By: rlamb@launchdarkly.com <4955475+kinyoklion@users.noreply.github.com>
@kinyoklion
kinyoklion merged commit 8a98583 into main Aug 20, 2026
15 checks passed
@kinyoklion
kinyoklion deleted the devin/config-with-wrapper-information branch August 20, 2026 17:30
jsonbailey pushed a commit that referenced this pull request Aug 28, 2026
🤖 I have created a release *beep* *boop*
---


##
[9.17.0](9.16.1...9.17.0)
(2026-08-28)


### Features

* Add async big segment store manager and async Redis adapter
([#462](#462))
([aa492d2](aa492d2))
* Add async DynamoDB persistent feature store
([#490](#490))
([cb010df](cb010df))
* Add async event processor
([ec7c113](ec7c113))
* Add async event processor
([#472](#472))
([ec7c113](ec7c113))
* Add async FDv1 polling data source and feature requester
([#475](#475))
([cca37a8](cca37a8))
* Add async FDv1 streaming and data source status tracking
([#464](#464))
([4bf7067](4bf7067))
* Add async FDv2 data sources
([#485](#485))
([5da1515](5da1515))
* Add async FDv2 data system
([#486](#486))
([6a70132](6a70132))
* Add async hook, plugin, and flag tracker
([#463](#463))
([686a70a](686a70a))
* Add async migration support
([#470](#470))
([577d51e](577d51e))
* Add async persistent feature store foundation and Redis adapter
([f9c76ee](f9c76ee))
* Add AsyncConfig for the async SDK client
([#471](#471))
([0587a78](0587a78))
* Add AsyncLDClient with FDv1 data system and public API
([#480](#480))
([fd041a5](fd041a5))
* Add Config.with_wrapper_information
([#501](#501))
([8a98583](8a98583))
* Add environment ID support for hooks.
([#484](#484))
([49e809f](49e809f))
* Add read-only store views and async persistence foundation for the
data system
([#503](#503))
([0eb61fa](0eb61fa))


### Bug Fixes

* Allow tombstones without a key property
([#502](#502))
([5f44e61](5f44e61))
* Escape attribute names reported in redactedAttributes
([#505](#505))
([90059cb](90059cb))
* Prevent a persistent-store outage from throwing in the sync FDv2
evaluation
([#506](#506))
([467da53](467da53))
* Return empty prerequisites for a flag that fails to evaluate in
all_flags_state
([#483](#483))
([73e9b07](73e9b07))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Overview**
> **Release Please** bumps the package from **9.16.1** to **9.17.0** in
`pyproject.toml`, `ldclient/version.py`,
`.release-please-manifest.json`, and the provenance example in
`PROVENANCE.md`.
> 
> `CHANGELOG.md` gains a new **9.17.0** (2026-08-28) section that
records what ships in this minor release: a broad **async** surface
(`AsyncLDClient`, `AsyncConfig`, async FDv1/FDv2 data systems, event
processor, hooks/plugins, migration, and Redis/DynamoDB persistent
stores plus big-segment async support), plus sync improvements
(`Config.with_wrapper_information`, hook environment ID, read-only store
views) and bug fixes (tombstones, `redactedAttributes` escaping, FDv2
persistent-store resilience, `all_flags_state` prerequisites).
> 
> No application logic changes appear in this diff—only version metadata
and release notes.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
f225e46. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
kinyoklion added a commit to launchdarkly/openfeature-python-server that referenced this pull request Aug 31, 2026
The provider now identifies itself as the wrapper, so requests it makes
are attributed to the OpenFeature provider rather than to the Python
SDK.

- Sets `wrapper_name` to `open-feature-python-server` and
`wrapper_version` to the provider version
- Uses `Config.with_wrapper_information`, released in
launchdarkly-server-sdk 9.17.0
- Raises the SDK floor to `>=9.17.0`
- Adds `ld_openfeature/version.py`, kept current by release-please

**Requirements**

- [x] I have added test coverage for new or changed functionality
- [x] I have followed the repository's [pull request submission
guidelines](../blob/main/CONTRIBUTING.md#submitting-pull-requests)
- [x] I have validated my changes against all supported platform
versions

<details>
<summary>Implementation details</summary>

**Describe the solution you've provided**

```python
self.__client = LDClient(config.with_wrapper_information(WRAPPER_NAME, VERSION))
```

This matches the Java and .NET providers, which derive a config from the
application-supplied one:

```java
new LDClient(sdkKey, LDConfig.Builder.fromConfig(config)
    .wrapper(Components.wrapperInfo()
        .wrapperName("open-feature-java-server")
        .wrapperVersion(Version.SDK_VERSION)).build());
```

The provider version comes from a new `ld_openfeature/version.py`, added
to `extra-files` in `release-please-config.json` so the constant is
bumped with each release the same way `docs/conf.py` already is.

**Describe alternatives you've considered**

An earlier revision of this PR copied the `Config` and assigned its
name-mangled private wrapper fields, because wrapper information could
only be supplied to the constructor. That depends on SDK internals,
hence the SDK change instead
([python-server-sdk#501](launchdarkly/python-server-sdk#501)).

**Testing**

`test_provider_identifies_itself_as_the_wrapper` asserts the client's
config carries the provider's name and version, and that the config
passed in by the caller is unchanged. Against the released 9.17.0: 80
tests pass, mypy clean.

</details>


Link to Devin session:
https://app.devin.ai/sessions/0c452d209ec54b068ba120b4c92b8f6c
Open in Devin Desktop:
https://app.devin.ai/desktop/session/0c452d209ec54b068ba120b4c92b8f6c?variant=devin
Requested by: @kinyoklion

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Overview**
> LaunchDarkly traffic from this provider is now attributed to the
OpenFeature integration instead of looking like plain Python SDK usage.
> 
> `LaunchDarklyProvider` builds the underlying `LDClient` from
`config.with_wrapper_information("open-feature-python-server", VERSION)`
so wrapper name and version are set on the client config without
mutating the caller’s `Config`. The version lives in new
`ld_openfeature/version.py`, wired into release-please alongside
`docs/conf.py`.
> 
> The minimum `launchdarkly-server-sdk` dependency is raised to
**>=9.17.0** for `with_wrapper_information`. A test asserts the client
carries the expected wrapper fields and that the original config’s
`wrapper_name` stays unset.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
8f3861b. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: Devin AI <devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants